
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.ComponentModel.Design;
using System.Web.UI.WebControls;
using System.Xml;

using Swaf.Container;
using Swaf.Gui;
using Swaf.Resource.Localization;

namespace Swaf.Gui.AspNet2
{
	/// <summary>
	/// This interface is implemented by classes that represent content in the page.  When the raw
	/// html text is first processed, it is turned into an ordered list of IPageContent elements.
	/// This allows for faster processing when using the control over and over again as would 
	/// happen in normal web usage.
	/// </summary>
	public interface IPageContent
	{
		/// <summary>
		/// When the page asks the UserControl to create its controls, it just loops through
		/// its IPageContent elements and calls this method.  This method will ask the element
		/// to create itself or any child controls it has.
		/// </summary>
		/// <param name="parent">The parent control this IPageContent is contained in.</param>
		/// <param name="postFixId">If the content is from an HtmlBlock then it has an id
		/// that is postfixed to the end of the ids of its controls.  This is the unique id it
		/// should postfix onto its controls. Can be null if not in an HtmlBlockInfo</param>
		/// <param name="parentBlock">If applicable, this is the parent HtmlBlockInfo the content
		/// is contained in.  Can be null if the content is not in an HtmlBlockInfo.</param>
		void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated);

		/// <summary>
		/// When the UserControl this is contained in is asked to render it just loops through
		/// its IPageContent elements and calls this method to render thier content.
		/// </summary>
		/// <param name="parent">The parent UserControl this is contained in.</param>
		/// <param name="output">The HtmlTextWriter this should use to output its contents.</param>
		/// <param name="postFixId">The postfixid of this content if embedded in an HtmlBlock.
		/// Can be null if not embedded in one.</param>
		/// <param name="parentBlock">The ParentBlock this control is embedded in.  Can be null if not
		/// embedded in an HtmlBlock.</param>
		void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock);
		int ChildCount { get;}
	}

	/// <summary>
	/// IPageContent elements are organized in a tree structure, this interface supports an IPageContent
	/// that has child IPageContent objects.
	/// </summary>
	public interface IPageParentContent : IPageContent
	{
		/// <summary>
		/// When the raw HTML text is being processing for the UserControl, each seperate content is
		/// passed into this as processed.  
		/// </summary>
		/// <param name="content">The page content found in the HTML text for the UserControl.</param>
		void addContent(IPageContent content);
	}

	/// <summary>
	/// Represents a chunk of raw HTML that does not have any server controls or other server content.
	/// When the raw HTML is being processed into a collection of IPageContent objects, this class 
	/// is used to maintain the simple HTML found inbetween server side content.
	/// </summary>
	public class SimpleHtmlPageContent : IPageContent
	{
		protected string m_html;
		/// <summary>
		/// Initializes this class with the simple html it will render.</summary>
		public SimpleHtmlPageContent(string html)
		{
			m_html = html;
		}
		public int ChildCount { get { return 1; } }
		/// <summary>
		/// Does nothing as this is simple, pure client side html.  There are no server side controls 
		/// in it to create.</summary>
        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
			//Intentionally blank.  No controls to create in simple html
		}

		/// <summary>
		/// Will simply stream its content into the HtmlTextWriter given.</summary>
		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
			output.Write(m_html);
		}
	}

    public abstract class BaseCallerPageContent : ServerControlLocalizer
    {
        string m_name;
        MethodInfo m_method;
        PropertyInfo m_prop;
        protected IList m_methodParams;
        protected string m_localizationKey = null;
        protected PropertyInfo m_nameProperty = null;
        protected static bool s_devMode = true;

        public static bool DevMode { get { return s_devMode; } set { s_devMode = value; } }

        protected void setupMethodInfo(Type objType, string methodName, string paramInfo)
        {
            m_name = methodName;
            MemberInfo[] callMembers = objType.GetMember(methodName);
            MemberInfo callMember = null;
            if (callMembers != null && callMembers.Length != 0)
            {
                callMember = callMembers[0];
                if (callMember.MemberType == MemberTypes.Method)
                {
                    m_method = callMember as MethodInfo;
                    m_methodParams = processCallParams(paramInfo);
                }
                else if (callMember.MemberType == MemberTypes.Property)
                    m_prop = callMember as PropertyInfo;
                object[] attrs = callMember.GetCustomAttributes(typeof(LocalizationAttribute), true);
                if (attrs != null && attrs.Length != 0 && attrs[0] is LocalizationAttribute &&
                    Swaf.Application.globalApp != null)
                {
                    LocalizationAttribute lattr = (LocalizationAttribute)attrs[0];
                    m_localizationKey = lattr.Key;
                    if (lattr.KeyIsPropName)
                    {
                        m_nameProperty = objType.GetProperty(lattr.Key);
                        m_localizationKey = "";
                    }
                }
            }
        }

        protected bool ValidCall { get { return (m_method != null || m_prop != null); } }

        protected bool shouldLocalize(UserControl parent)
        {
            return m_localizationKey != null && parent.Localized == true;
        }

        protected object call(UserControl parent, object p1, Type p1Type)
        {
            object results = null;
            bool foundLocalizedValue = true;
            IApplication app = Application.currentApp;
            string pageName = "";
            string name = m_name;
            string key = m_localizationKey;

            if (shouldLocalize(parent))
            {
                pageName = calcPageName(parent.Page);
                if (key == "")
                {
                    if (m_nameProperty != null)
                        name = m_nameProperty.GetValue(parent, null).ToString();

                    key = string.Format("{0}.{1}.{2}.{3}",
                        pageName, parent.Parent.ID, parent.ID, name);
                }

                results = app.resMgr.getMessage(key, null);
                if (results == null)
                {
                    app.Log.println("Localization: Unable to find key: {0}", key);
                    foundLocalizedValue = false;
                }
            }

            if(results == null)
                if (m_method != null)
                {
                    ParameterInfo[] pInfos = m_method.GetParameters();
                    object[] methodParamArray = new object[pInfos.Length];
                    int pos = 0;
                    foreach (ParameterInfo pInfo in pInfos)
                    {
                        Type pType = pInfo.ParameterType;
                        if (pType.IsAssignableFrom(p1Type))
                        {
                            if (p1 == null)
                                return null;
                            methodParamArray[pos] = p1;
                        }
                        else
                            methodParamArray[pos] = Cmn.createType(pType, (string)m_methodParams[pos]);
                        pos++;
                    }
                    results = m_method.Invoke(parent, methodParamArray);
                }
                else if (m_prop != null)
                    results = m_prop.GetValue(parent, null);

            if (shouldLocalize(parent) && TrackLocalizedProperties)
				trackLocalizationAttempt(app, parent.Page, pageName,
                    string.Format("{0}.{1}", parent.Parent.ID, parent.ID),
                    key, results==null?"":results.ToString(), foundLocalizedValue);

            if (!foundLocalizedValue && s_showCultureOnNoLocalizationHit)
            {
                string strVal = results as string;
                if (strVal != null && strVal != "")
                    results = string.Format("[{0}] {1}", app.resMgr.CurrentCulture.get("browserCode"), strVal);
            }

            return results;
        }

        protected IList processCallParams(string paramInfo)
        {
            IList paramList = new ArrayList();
            if (paramInfo != null)
            {
                bool inStr = false;
                StringBuilder buff = new StringBuilder(50);
                foreach (char c in paramInfo)
                {
                    if (c == ',' && !inStr)
                    {
                        paramList.Add(buff.ToString());
                        buff.Length = 0;
                    }
                    else if (c == '"')
                        inStr = !inStr;
                    else
                        buff.Append(c);
                }
                if (buff.Length > 0)
                    paramList.Add(buff.ToString());
            }
            return paramList;
        }
    }

    public class MethodCallPageContent : BaseCallerPageContent, IPageContent
	{
        public MethodCallPageContent(HtmlControlHelper helper, string methodInfo)
		{
			string callCode = methodInfo.Substring(3, methodInfo.Length - 5);
			callCode = callCode.Trim();
			Regex pattern = new Regex(@"(\w+)\((.*)\)");
			Match m = pattern.Match(callCode);
            if (m.Success)
                base.setupMethodInfo(helper.Parent.GetType(), m.Groups[1].Value, m.Groups[2].Value);
            else
             //In no match on the method format regex, assume this is a property get
                base.setupMethodInfo(helper.Parent.GetType(), callCode, null);
		}

		public int ChildCount { get { return 1; } }
        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
            if (!parentBlock.IsRootContent)
            {
                HtmlBlock block = parentBlock.Context["_currentBlock"] as HtmlBlock;
                block.BlockContext[this] = call(parent, null, typeof(HtmlTextWriter));
            }
		}

		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
            object results = parentBlock.BlockContext[this];
            if (results == null)
                results = call(parent, output, output.GetType());
            if(results != null)
                output.Write(results.ToString());
        }
	}

	/// <summary>
	/// This class maintains the information for a server control found in the raw HTML text stream
	/// for the UserControl.
	/// </summary>
    public class ServerControlPageContent : ServerControlLocalizer, IPageContent
	{
		/// <summary>
		/// Should always be 3 strings.  The first string in the array should be the HTML text up to the
		/// ID=" but not the actual id itself. The second string is the ID of the server control. The 3rd
		/// element is the " and the rest of the server control text.  This is used to combine these elements
		/// to use the ParseControl.  See the createControls method of this class.
		/// </summary>
		protected string[] m_controlInfo;
		/// <summary>
		/// Helps in sizing the StringBuffer for the server control text when the server side control
		/// text is parsed to create the actual control object.  This is a performance optimizer.
		/// </summary>
		protected int m_initCapacity = 0;

        protected IDictionary m_controlAttributes = new Hashtable();

        protected LocalizationInfo m_localizatedFields = null;
        /// <summary>
		/// Takes the 3 element string array and stores it as well as calculates the inital capacity of
		/// the string buffer used when building the server control text to be processed by the 
		/// ParseControl method of Page or IDesignHost.
		/// </summary>
		/// <param name="controlInfo">Absolutely must be a 3 element array as described elsewhere in this
		/// classes comments.</param>
		public ServerControlPageContent(string[] controlInfo)
		{
			Debug.Assert(controlInfo.Length == 4);
			m_controlInfo = controlInfo;
			m_initCapacity = controlInfo[0].Length + controlInfo[1].Length + controlInfo[2].Length + controlInfo[3].Length + 100;
            try
            {
				string ctlSub =  controlInfo[1] + controlInfo[2] + controlInfo[3];
				string ctlTxt = controlInfo[0] + ctlSub;
                Regex r = new Regex(@"\s+([^=\s]+)\s*=\s*([""'])(.+?)(?<!\\)\2");
				int i = ctlSub.IndexOf(">");
                MatchCollection mc = r.Matches(ctlSub.Substring(0,i));
                foreach (Match m in mc)
					m_controlAttributes[m.Groups[1].Value.ToLower()] = m.Groups[3].Value.Replace("%gt;", ">");
            }
            catch
            {
            }
		}
		public int ChildCount { get { return 1; } }

		protected bool IsContainedTemplateControl(string ctrlInfo)
		{
			if (ctrlInfo.StartsWith("<swf:EndContainingDiv"))
				return true;
			return false;
		}
		/// <summary>
		/// Will create the server control and added it to the UserControl's Control collection.</summary>
        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
			bool isContainedTemplate = IsContainedTemplateControl(m_controlInfo[1]);
			string elementContent = null;
			string innerTemplate = null;
			if (isContainedTemplate)
			{
				StringBuilder buff = new StringBuilder();
				buff.Append(m_controlInfo[1]);
				buff.Append(m_controlInfo[2]);
				if (postFixId != null)
					buff.Append(postFixId);
				buff.Append(m_controlInfo[3]);
				elementContent = buff.ToString();
				Regex r = new Regex(@"(?'startBraket'<(?'name'[^\s]+)[^>]+>)(?'template'.*?)(?'endBraket'</\k'name'>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
				Match m = r.Match(elementContent);
				if (m.Success)
				{
					buff = new StringBuilder();
					buff.Append(m_controlInfo[0])
						.Append(m.Groups["startBraket"].Value)
						.Append(m.Groups["endBraket"].Value);
					elementContent = buff.ToString();
					innerTemplate = m.Groups["template"].Value;
				}
			}
			else
			{
				StringBuilder buff = new StringBuilder(m_controlInfo[0], m_initCapacity);
				buff.Append(m_controlInfo[1]);
				buff.Append(m_controlInfo[2]);
				if (postFixId != null)
					buff.Append(postFixId);
				buff.Append(m_controlInfo[3]);
				elementContent = buff.ToString();
			}

			Control nc = null;
			if (parent.DesignHost == null)
			{
				if (parent.Page != null)
					nc = parent.Page.ParseControl(elementContent);
			}
			else
				nc = ControlParser.ParseControl(parent.DesignHost, elementContent);

			if (nc != null && !parent.isInDesignMode())
			{
				Control oldnc = nc;
				nc = nc.Controls[0];
				//nc.Parent.Controls.Remove(nc);
				// oldnc.Controls.Remove(nc);
			}
			if (nc != null)
			{
				processControl(nc, controlsCreated, postFixId, parent);
				try
				{
					parent.OrigionalControls[nc.ID] = m_controlAttributes;
					parent.Controls.Add(nc);
				}
				catch
				{
					nc.EnableViewState = false;
					parent.Controls.Add(nc);
					nc.EnableViewState = true;
				}
			}

			if (innerTemplate != null && nc is IInnerTemplateControl)
				((IInnerTemplateControl)nc).Template = innerTemplate;
		
		}

		protected void processControl(Control ctl, IList controlsCreated, string postFixId, UserControl parent)
		{
			string oldId = ctl.ID;
			if (oldId != null)
			{
				if (!Cmn.IsEmpty(ctl.SkinID))
					parent.processChildSkinId(ctl);
				if (!ctl.ID.EndsWith(postFixId))
					ctl.ID = ctl.ID + postFixId;
				else
					oldId = ctl.ID.Substring(0, ctl.ID.Length - postFixId.Length);
				if (m_localizatedFields == null)
					lock (this)
						if (m_localizatedFields == null)
							m_localizatedFields = calculateLocalizedProperties(ctl);

				if (controlsCreated != null)
					if (postFixId != null)
						controlsCreated.Add(new string[] { ctl.ID, oldId });
					else
						controlsCreated.Add(new string[] { m_controlInfo[2], m_controlInfo[2] });
			}
			foreach (Control childCtl in ctl.Controls)
				processControl(childCtl, controlsCreated, postFixId, parent);
		}

		/// <summary>
		/// Will tell the server control managed by this class to render its contents.</summary>
		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
			string id = m_controlInfo[2];
			if (postFixId != null)
				id += postFixId;
			Control nc = null;
			foreach (Control childCtl in parent.Controls)
				if (childCtl.ID != null && (childCtl.ID.CompareTo(id) == 0 || childCtl.ID.StartsWith(id + "_")))
				{
					nc = childCtl;
					break;
				}
            if (nc != null)
            {
				if (m_localizatedFields != null && !Cmn.IsTrue(app.sessionData["NoCulture"]))
                    base.localizeControl(nc, m_localizatedFields, parent.Page, parent.ID);

                nc.RenderControl(output);
            }
            else
            {
                //Its ok if the control can no longer be found.  Is is possible that the control got removed
                //by code from the user control due to its own logic.  Do nothing if it cannot be found.
            }
		}
	}

	
	/// <summary>
	/// Represents the root element of all the content within the usercontrol raw HTML.  When the raw
	/// html is parsed, this is the first object created.  All the various types of top level content
	/// are placed within this as children.</summary>
	public class BasePageContent : IPageParentContent
	{
		/// <summary>
		/// The list of the top level child elements contained within the page.  This list will not
		/// contain elements contained within other elements.  For example, simple html contained within
		/// an HtmlBlock will not be in this list, but will be a child wihtin the HtmlBlock which would
		/// be in this list.  This is the list of direct children of the page.  It is also an ordered
		/// list based on the order the items were found in the raw html for the user control.
		/// </summary>
		protected IList m_content = new ArrayList();

		/// <summary>
		/// Creates the root element by having it turn around and ask the HtmlControlHelper
		/// to continue its processing of all of the html contained within the TextReader given.
		/// This is the method that kicks off the main processing of the page.
		/// </summary>
		/// <param name="helper">The instances of the HtmlControlHelper to use to process the HTML
		/// read from the TextReader.</param>
		/// <param name="r">A valid text reader at the begining of the HTML content to be rendered in
		/// the ASP.NET page for the user control.</param>
		public BasePageContent(HtmlControlHelper helper, TextReader r)
		{
			helper.processContent(this, r, null);
		}

		/// <summary>
		/// As the HtmlControlHelper processes the content within the page it will call this method
		/// as it sees a change in the content of the html text.  For example if the page starts
		/// out with simple html and then has an embedded server side control, it will first
		/// add a SimpleHtmlPageContent object then add a ServerControlPageContent object via
		/// this method.  This method should be called in the order the elements in the html are
		/// discovered.  This will be the order the content is rendered in.</summary>
		/// <param name="content">The child element of the base page to be added</param>
		public void addContent(IPageContent content)
		{
			m_content.Add(content);
		}

		public int ChildCount { get { return m_content.Count; } }

		/// <summary>
		/// Called when the user control is first being loaded by the ASP.NET page and is being asked
		/// to create its child controls.  This will iterate through all of its child elements and
		/// ask them to create their children.  This class itself has no server controls to create
		/// but could contain children in its content that do.</summary>
        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
            if (parentBlock == null)
                parentBlock = new HtmlBlockInfo(null, parent, null);
			foreach (IPageContent content in m_content)
				content.createControls(app, parent, postFixId, parentBlock, controlsCreated);
		}

		/// <summary>
		/// When the ASP.NET is ready to have its server side controls render their content, this method
		/// will get called by UserControl.  Because this class doesn't directly have anything to render
		/// it simply iterates through its child IPageContent objects and asks them to render their
		/// conent.</summary>
		/// <param name="parent">The UserControl this is an child of</param>
		/// <param name="output">The place to write content to go to the browser.</param>
		/// <param name="postFixId">Needed by HtmlBlock controls only, not used here.</param>
		/// <param name="parentBlock">Needed only by childrne of an HtmlBlock, not used here.</param>
		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
			foreach (IPageContent content in m_content)
				content.renderContent(app, parent, output, postFixId, parentBlock);
		}
	}

    enum ConditionalType { equals, notEquals };
	public class ConditionalContent : IPageParentContent
	{
		protected IList m_content = new ArrayList();
		protected string m_processor = "";
		protected string[] m_condition;
		protected string[] m_callParam = null;
        ConditionalType m_type;

		protected static Regex s_call = new Regex(".*on\\s*=\\s*\"(.+)(==|!=)(.+)\".*");
		protected static Regex s_param = new Regex(@"([^\(]+)\(([^\)]+)\)");
		public ConditionalContent(HtmlControlHelper helper, string baseHtml, HtmlBlockContent parentBlock)
		{
			Match m = s_call.Match(baseHtml);
            if (m.Success)
            {
                m_condition = new string[] { m.Groups[1].Value, m.Groups[3].Value };
                if (m.Groups[2].Value == "==")
                    m_type = ConditionalType.equals;
                else
                    m_type = ConditionalType.notEquals;
				m = s_param.Match(m_condition[0]);
				if (m.Success)
				{
					m_condition[0] = m.Groups[1].Value;
					m_callParam = new string[] { m.Groups[2].Value } ;
				}
            }
		}
		public int ChildCount { get { return m_content.Count; } }
		#region IPageParentContent Members

		public void addContent(IPageContent content)
		{
			m_content.Add(content);
		}

		#endregion

		#region IPageContent Members

        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
            object condVal = null;
            if(parentBlock != null)
                condVal = parentBlock.Context[NameAddress.getFirstAddress(m_condition[0])];
			if (condVal == null)
			{
				if (m_callParam != null && m_callParam.Length != 0)
					condVal = Cmn.callMethod(parent, NameAddress.getFirstAddress(m_condition[0]), new Type[]{typeof(string)}, m_callParam);
				else
					condVal = Cmn.callPropertyGet(parent, NameAddress.getFirstAddress(m_condition[0]), m_callParam);
			}
            if (condVal == null)
                condVal = "null";
			if (condVal != null && condVal is INameAddressSupport && !NameAddress.isSingleAddress(m_condition[0]))
				condVal = ((INameAddressSupport)condVal).get(NameAddress.getRemainingAddress(m_condition[0]));

            HtmlBlock block = parentBlock.Context["_currentBlock"] as HtmlBlock;
            if (condVal != null &&
               ((m_type == ConditionalType.equals && condVal.ToString() == m_condition[1]) ||
               (m_type == ConditionalType.notEquals && condVal.ToString() != m_condition[1])))
            {
                block.BlockContext[this] = true;
                foreach (IPageContent content in m_content)
                    content.createControls(app, parent, postFixId, parentBlock, controlsCreated);
            }
            else
                block.BlockContext[this] = false;
		}

		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
			if(Cmn.IsTrue(parentBlock.BlockContext[this]))
				foreach (IPageContent content in m_content)
					content.renderContent(app, parent, output, postFixId, parentBlock);
		}

		#endregion
	}
	
	/// <summary>
	/// Maintains the various elements found within an HtmlBlock embedded within the user control
	/// as well as the method on the user control that will process the html block to create 0
	/// or more instances of it on the page.
	/// </summary>
    public class HtmlBlockContent : BaseCallerPageContent, IPageParentContent
	{
        protected static Regex s_methodRegex = new Regex(@".*method\s*=\s*""(\w+?)(\((.*?)\))?"".*", RegexOptions.IgnoreCase);
        protected static Regex s_idRegex = new Regex("\\s+id\\s*=\\s*\"(.*?)\".*", RegexOptions.IgnoreCase);
		/// <summary>
		/// Used only during design mode, this method helps to generate a single unique id for each HtmlBlock
		/// instance for each HtmlBlock on a given page.  Normally the unique Id is driven by the child UserControl
		/// class that creates instances of the HtmlBlock.</summary>
		protected static int s_uniqueId = 0;
		/// <summary>
		/// The child IPageContent elements in the order they were processed in the raw HTML.</summary>
		protected IList m_content = new ArrayList();
		protected HtmlBlockContent m_parentBlock = null;
        protected string m_id = null;
        /// <summary>
        /// This is only used in the root HtmlBlock that represents the entire page.  Any htmlblock that has
        /// its id set will be contained within this dictionary so it can be referenced if needed.
        /// </summary>
        protected IDictionary m_namedBlocks;

        public static IDictionary getRootNamedBlocks(UserControl parent)
        {
            HtmlBlockContent root = parent.getPageAndRenderIfNeeded() as HtmlBlockContent;
            return (root != null) ? root.NamedBlocks : null;
        }

		public int ChildCount { get { return m_content.Count; } }
		public IDictionary NamedBlocks
        {
            get
            {
                if (m_namedBlocks == null)
                    m_namedBlocks = new Hashtable();
                return m_namedBlocks;
            }
        }

        public HtmlBlockContent()
        {
        }

		/// <summary>
		/// This method will process just the first server side html that defines there is an
		/// html block.  The baseHtml should not contain the full html for the entire block, just
		/// the <% HtmlBlock method="..."%> section.</summary>
		/// <param name="helper"></param>
		/// <param name="baseHtml"></param>
		/// <param name="parentBlock"></param>
		public HtmlBlockContent(HtmlControlHelper helper, string baseHtml, HtmlBlockContent parentBlock)
		{
            int startEndPos = baseHtml.IndexOf("%>");
            string blockInfo = baseHtml.Substring(0, startEndPos);
            Match m = s_methodRegex.Match(blockInfo);
            if (m.Success)
                base.setupMethodInfo(helper.Parent.GetType(), m.Groups[1].Value, m.Groups[3].Value);

            m = s_idRegex.Match(blockInfo);
            if (m.Success)
            {
                m_id = m.Groups[1].Value;
                getRootNamedBlocks(helper.Parent)[m_id] = this;
            }

            m_parentBlock = parentBlock;
        }

		/// <summary>
		/// Creates the root element by having it turn around and ask the HtmlControlHelper
		/// to continue its processing of all of the html contained within the TextReader given.
		/// This is the method that kicks off the main processing of the page.
		/// </summary>
		/// <param name="helper">The instances of the HtmlControlHelper to use to process the HTML
		/// read from the TextReader.</param>
		/// <param name="r">A valid text reader at the begining of the HTML content to be rendered in
		/// the ASP.NET page for the user control.</param>
        public HtmlBlockContent(HtmlControlHelper helper, TextReader r)
		{
			helper.processContent(this, r, null);
		}

		/// <summary>
		/// The list of child IPageConent objects embedded within this HtmlBlock</summary>
		public IList Content { get { return m_content; } }

		/// <summary>
		/// During processing of the raw HTML for this user control, this will be called for each
		/// direct child IPageContent within the HtmlBlock.</summary>
		/// <param name="content"></param>
		public void addContent(IPageContent content)
		{
			m_content.Add(content);
		}

		/// <summary>
		/// The HtmlBlock IPageContent doesnt have any child controls directly to create, but it will
		/// invoke its processing method which may create 0 or more instances of the child controls contained
		/// within it.  This class does not maintain the instances of the HtmlBlocks created by the controls,
		/// it simply triggers the processing method.  If in design mode, this method will not invoke the
		/// processing method but will create a single instance of its child elements so that the designer
		/// will show one set of controls.</summary>
        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
		{
            if (parent.isInDesignMode() && ValidCall)
            {
                string id = (++s_uniqueId).ToString();
                foreach (IPageContent content in m_content)
                    content.createControls(app, parent, id, parentBlock, controlsCreated);
            }
            else if (ValidCall)
                call(parent, new HtmlBlockInfo(this, parent, parentBlock), typeof(HtmlBlockInfo));
            else
                new HtmlBlockInfo(this, parent, parentBlock).generateBlock("");
		}

		/// <summary>
		/// This method will render the content of all the instances of the HtmlBlock elements created by the
		/// processing method during the createControls call.  IF there are no HtmlBlocks then nothing will get
		/// rendered which is normal behavior.  It is up to the processing method to create at least 1 HtmlBlock 
		/// for anything to be displayed by this HtmlBlock.</summary>
		/// <param name="parent">The UserControl this element is contained within</param>
		/// <param name="output">The place to render content to be sent to the browser</param>
		/// <param name="postFixId">Only not null if this HtmlBlock is contained within an HtmlBlock.</param>
		/// <param name="parentBlock">Only not null if this HtmlBlock is contained within an HtmlBlock</param>
		public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
		{
			IList blocks = null;
			if (parentBlock != null)
				blocks = parentBlock.getChildBlocks(this);
			else
				blocks = (IList)parent.BlockInfo[this];
			if (blocks != null)
				foreach (HtmlBlock b in blocks)
					foreach (IPageContent content in m_content)
						content.renderContent(app, parent, output, b.Id, b);
		}
	}

    public class JumpToHtmlBlockContent : IPageContent
    {
        protected static Regex s_idRegex = new Regex(".*id\\s*=\\s*\"(.*)\".*", RegexOptions.IgnoreCase);
        protected string m_jumpBlockId = null;

        public JumpToHtmlBlockContent(HtmlControlHelper helper, string info)
        {
            Match m = s_idRegex.Match(info);
            if (m.Success)
                m_jumpBlockId = m.Groups[1].Value;
        }
		public int ChildCount { get { return 1; } }

        #region IPageContent Members

        public void createControls(IApplication app, UserControl parent, string postFixId, HtmlBlockInfo parentBlock, IList controlsCreated)
        {
            if (m_jumpBlockId != null)
            {
                HtmlBlockContent block = HtmlBlockContent.getRootNamedBlocks(parent)[m_jumpBlockId] as HtmlBlockContent;
                if(block != null)
                    block.createControls(app, parent, postFixId, parentBlock, controlsCreated);
            }
        }

        public void renderContent(IApplication app, UserControl parent, HtmlTextWriter output, string postFixId, HtmlBlock parentBlock)
        {
            if (m_jumpBlockId != null)
            {
                HtmlBlockContent block = HtmlBlockContent.getRootNamedBlocks(parent)[m_jumpBlockId] as HtmlBlockContent;
                if (block != null)
                    block.renderContent(app, parent, output, postFixId, parentBlock);
            }
        }

        #endregion
    }

	/// <summary>
	/// Used during the phase of control creation, this class is passed to the Child class that Derives
	/// from user control via the processor method provided in the HtmlBlock element embedded within the
	/// raw HTML.  This class provides the ability to instantiate zero or more instances of the HTML block
	/// the processor is providing data for.
	/// </summary>
	/// <remarks>
	/// When the HtmlBlockContent.createControls method is called it will call the processor method
	/// passing in an instance of this class.  This class gives the processor the ability to to call
	/// the HtmlBlockInfo.generateBlock method, giving an id to postfix to all the server controls
	/// defined in it.  This is to help diferentiate between multiple instances of the same set of
	/// server controls.  For example, if the HTML block has an <asp:TextBox id="name" /> embedded
	/// within it then if the processor creates more then one it would normally have problems with
	/// multiple server controls containing the same id.
	/// 
	/// The purpose of the HtmlBlock is to allow the processor to generateBlock for each element in
	/// a list, such as each IBizObj.Customer in an IBizObjList.  Typically, the id passed into the
	/// generateBlock method is the key alue for the IBizOBj the block is generated for. Later, when
	/// events are processed or the contents of the server controls need to be updated with data 
	/// (like an asp:TextBox needing to have its text set from a field within the given bizobj)
	/// the specific server control can be found by using the baseid of the server control with the
	/// key value for the given bizobj postfixed to it.  So given an asp:TextBox with an id of "name"
	/// and an IBizObj with a key value of "123", the actual id of the TextBox to use is "name123".
	/// 
	/// The server controls created via generateBlock support all of the normal functionality of
	/// a server control embedded directly in an ASP.NET page.  They can have even handlers, work properly
	/// with post back and view state data and so forth.  The only thing different is that it is typically
	/// not supported to have local variables for the controls because it really shouldn't be known at
	/// code time what the actual ids of the controls are.  This means the controls must be found via
	/// the UserControl.FindControl(id) method.  
	/// 
	/// An HtmlBlock can be embedded within a parent HtmlBlock and this can go on virtually indefinately,
	/// meaning an HtmlBlock can be embedded within an HtmlBlock which is embedded within an HtmlBlock
	/// and so forth.  The purpose of doing this is to display a list of elements from with a child of
	/// a list of elements.  For example, the top HtmlBlock could be for a list of Customers, while the
	/// embedded HtmlBlock is for the list of Addresses of each Customer.  This allows a tree of
	/// data to be displayed with a hiararchy of HtmlBlock elements easily.  When HtmlBlocks are embedded
	/// within an HtmlBlock, they share a common context dictionary the processors can use to share information.
	/// For example, while iterating through the list of Customers in the list, the top level HtmlBlock
	/// processor could put the current Customer object within the context dictionary so that when it 
	/// calls the generateBlock method and the child HtmlBlock's processor method gets called, it could
	/// get the list of addresses from the Customer to use.
	/// 
	/// For a deeper understanding of how to use HtmlBlocks, the framework gallery sample application makes
	/// extensive use of the HtmlBlock to display various bizobjlists of data.</remarks>
	public class HtmlBlockInfo
	{
		HtmlBlockContent m_content;
		UserControl m_parent;
		IDictionary m_context = null;
		HtmlBlockInfo m_parentBlock;

		/// <summary>
		/// Constructed by the HtmlBlockContent class during the createControl processing.  Gets
		/// this object ready for use by the processor method for the HtmlBlock.
		/// </summary>
		/// <param name="content">The content of elements embedded within the HtmlBlock definition</param>
		/// <param name="parent">The user control parent the HtmlBlock is embedded in</param>
		/// <param name="parentBlock">If the HtmlBlock is embedded within another HtmlBlock, this is the
		/// parent block it is embedded in.  This is used to share a common context dictionary for the
		/// processing of data between the parent/child blocks.</param>
		public HtmlBlockInfo(HtmlBlockContent content, UserControl parent, HtmlBlockInfo parentBlock)
		{
			m_content = content;
			m_parent = parent;
			m_context = (parentBlock == null) ? new Hashtable() : parentBlock.Context;
			m_parentBlock = parentBlock;
		}

        public bool IsRootContent { get { return m_parentBlock == null; } }

		/// <summary>
		/// The shared context dictionary that is used between parent and child HtmlBlocks to share
		/// information during processing.  Not normally needed for a simple HtmlBlock that has no
		/// child blocks.</summary>
		public IDictionary Context { get { return m_context; } }

		/// <summary>
		/// Called by the processor method of the HtmlBlock to generate an instance of the child
		/// IPageContent objects for a specific id.  This method must be called at least once for
		/// any of the content to be displayed.  It is not an error to not call this, but if it
		/// isn't called no content is displayed.
		/// </summary>
		/// <param name="id">The id to postfix to the child server controls embedded within the
		/// HtmlBlock.  This id should be unique from all other calls to this method to avoid
		/// problems with duplicate ids for server controls in the user control.  There is
		/// no need to worry about the ids being used in other UserControls because the ids
		/// only have to be unique within the context of this user control.</param>
		public IList generateBlock(string id)
		{
            IList controlsCreated = new ArrayList();
			HtmlBlock myBlock = new HtmlBlock(id);
			HtmlBlock parentBlock = (HtmlBlock)Context["_currentBlock"];
			if (parentBlock == null)
			{
				IList blocks = (IList)m_parent.BlockInfo[m_content];
				if (blocks == null)
					m_parent.BlockInfo[m_content] = blocks = new ArrayList();
				blocks.Add(myBlock);
			}
			else
				parentBlock.getChildBlocks(m_content).Add(myBlock);

			Context["_currentBlock"] = myBlock;
            IApplication app = Swaf.Application.currentApp;
			foreach (IPageContent content in m_content.Content)
				content.createControls(app, m_parent, id, this, controlsCreated);
			Context["_currentBlock"] = parentBlock;

            return controlsCreated;
		}
	}

	/// <summary>
	/// Represents a specific instance of the HtmlBlock generated by calling the
	/// HtmlBlockInfo.generateBlock method.  IT keeps track of all the child blocks
	/// contained within it when there are child HtmlBlocks embedded within the HtmlBlock
	/// in the raw Html text for the UserControl</summary>
	public class HtmlBlock
	{
		public string Id;
		public IDictionary BlockContext = new Hashtable();
		public HtmlBlock(string id)
		{
			Id = id;
		}

        public IList getChildBlocks(HtmlBlockContent childBlock)
        {
            IList blocks = BlockContext[childBlock] as IList;
            if (blocks == null)
                BlockContext[childBlock] = blocks = new ArrayList();
            return blocks;
        }
	}

	/// <summary>
	/// This is the class that does all the work to process the raw HTML text for the
	/// UserConrol into a tree of IPageContent elements.  Normally this only happens once during the
	/// lifetime of a running ASP.NET application.  Once the raw HTML text of the user
	/// control is processed into the tree of IPageContent objects, the IPageContent objects
	/// are used to generate the output for the UserControl as the pages it is embedded in are
	/// processed over and over again during normal user interaction.  This helps improve 
	/// performance over processing the raw HTML on each page request.
	/// </summary>
	/// <remarks>
	/// This class is recursive in nature.  As it finds embedded HtmlBlocks and server controls 
	/// it calls itself to process the inner contents of the element.  This makes the code
	/// simpler and supports a virtually unlimited number of embedded elements within elements.
	/// 
	/// It also supports customer server controls as long as the <@ register> element is
	/// found within the UserControl HTML.  For example to use the FreeTextBox user control 
	/// from www.freetextbox.com the <@ Register> for the FreeTextBox must be specified once
	/// within the HTML text for the user control.  Then the <FTB:FreeTextBox> can be embedded
	/// as many times as is needed.</remarks>
	public class HtmlControlHelper
	{
		protected Page m_page;
		protected UserControl m_parent;
		protected static Regex s_contentIdRegex = new Regex("\\s+id\\s*=\\s*\"([^\"]*)\".*", RegexOptions.IgnoreCase);
		protected static Regex s_tagPrefixRegex = new Regex("\\s+tagprefix\\s*=\\s*\"([^\"]*)\".*", RegexOptions.IgnoreCase);

        IList m_tagPrefixes = new ArrayList();


		/// <summary>
		/// Initializes the HtmlControlHelper to get ready to process the HTML for the User Control
		/// </summary>
		/// <param name="page">The page the user control is embedded in.</param>
		/// <param name="parent">The user control this class is processing its HTML for.</param>
		public HtmlControlHelper(Page page, UserControl parent)
		{
			m_page = page;
			m_parent = parent;
            m_tagPrefixes.Add(new string[] { "asp", "" });
        }

		/// <summary>Access for the Page the user control is in.</summary>
		public Page Page { get { return m_page; } }
		/// <summary>Access for the parent UserControl the content is processing for.</summary>
		public UserControl Parent { get { return m_parent; } }

		/// <summary>
		/// The main processing method that uses the given TextReader r to iterate through
		/// the characters of the raw HTML text and turn it into a tree of IPageContent 
		/// elements.  This method is recursive.
		/// </summary>
		/// <remarks>
		/// This method is hard coded to recognize a specific set of server tags such as
		/// <%@> and <%%> elements as well as the <asp:> and custom server side elements
		/// as long as they are registered with the <%@ Register> element as is
		/// normally required anyway.  There is currently no way to expand the type of
		/// server processing element recognized and processed by this method without
		/// directly modifying it.
		/// 
		/// This method is somewhat complex in its processing given the nature of what
		/// it is doing.  Recursion makes it a much simpler method then it would
		/// be otherwise.</remarks>
		/// <param name="pageContent">The page content the current HTML being read 
		/// from the text reader belongs to.</param>
		/// <param name="r">The HTML text stream to read and process from.</param>
		/// <param name="endTag">The endTag to stop processing on when being called
		/// recursively.  Is null when being called for the first time for the
		/// top lelvel content.</param>
		/// <returns></returns>
		public string processContent(IPageParentContent pageContent, TextReader r, string endTag)
		{
			StringBuilder buff = new StringBuilder(2000);
			SimpleHtmlPageContent htmlContent;

			char c;
			do
			{
				c = (char)r.Read();
				//converting a -1 to a char causes it to be 65535, so this is a check if the TextReader is at EOF.
				if (c == 65535)
					break;
				//When starting an element we read till the end > and process it for the various
				//types of IPageContent to be created.
				if (c == '<')
				{
					string content = getElement(r);
					if (content.StartsWith("<%@ Register"))
					{
						Match m = s_tagPrefixRegex.Match(content);
						string tagPrefix = "";
						if (m.Success)
							tagPrefix = m.Groups[1].Value;
						m_tagPrefixes.Add(new string[] { tagPrefix, content });
					}
					else if (content.StartsWith("<%HtmlBlock"))
					{
						if (pageContent != null)
						{
							htmlContent = new SimpleHtmlPageContent(buff.ToString());
							buff = new StringBuilder(2000);
							pageContent.addContent(htmlContent);

							HtmlBlockContent blockContent = new HtmlBlockContent(this, content,
								(pageContent is HtmlBlockContent) ? (HtmlBlockContent)pageContent : null);
							processContent(blockContent, r, "<%EndHtmlBlock");
							pageContent.addContent(blockContent);
						}
						else
							buff.Append(content);
					}
                    else if (content.StartsWith("<%JumpToHtmlBlock"))
                    {
                        htmlContent = new SimpleHtmlPageContent(buff.ToString());
                        pageContent.addContent(htmlContent);
                        buff = new StringBuilder(2000);

                        JumpToHtmlBlockContent jumpBlock = new JumpToHtmlBlockContent(this, content);
                        pageContent.addContent(jumpBlock);
                    }
                    else if (content.StartsWith("<%Conditional"))
                    {
                        if (pageContent != null)
                        {
                            htmlContent = new SimpleHtmlPageContent(buff.ToString());
                            buff = new StringBuilder(2000);
                            pageContent.addContent(htmlContent);

                            ConditionalContent blockContent = new ConditionalContent(this, content,
                                (pageContent is HtmlBlockContent) ? (HtmlBlockContent)pageContent : null);
                            processContent(blockContent, r, "<%EndConditional");
                            pageContent.addContent(blockContent);
                        }
                        else
                            buff.Append(content);
                    }
                    else if (content.Contains("<%="))
                    {
                        htmlContent = new SimpleHtmlPageContent(buff.ToString());
                        pageContent.addContent(htmlContent);
                        buff = new StringBuilder(2000);

                        int index = content.IndexOf("<%=");
                        MethodCallPageContent methodCall = null;

                        if (index == 0)
                        {
                            methodCall = new MethodCallPageContent(this, content);
                            pageContent.addContent(methodCall);
                        }
                        else
                        {
                            int endIndex = content.IndexOf("%>", index);
                            SimpleHtmlPageContent html = new SimpleHtmlPageContent(content.Substring(0, index));
                            pageContent.addContent(html);
                            methodCall = new MethodCallPageContent(this, content.Substring(index, endIndex - index + 2));
                            pageContent.addContent(methodCall);
                        }
                    }
                    else if (content.StartsWith("<%@"))
                    {
                        //Ignore these.....
                    }
                    else
                    {
                        //Could be a start server control tag.  Compair against known server control tag
                        //prefix/names.
                        bool foundTagPrefix = false;
                        foreach (string[] tag in m_tagPrefixes)
                        {
                            if (content.Length > tag[0].Length && content.Substring(1, tag[0].Length) == tag[0])
                            {
                                if (pageContent != null)
                                {
                                    htmlContent = new SimpleHtmlPageContent(buff.ToString());
                                    buff = new StringBuilder(2000);
                                    pageContent.addContent(htmlContent);

                                    string ctlName = "";
                                    int p4 = content.IndexOf(' ');
                                    ctlName = content.Substring(1, p4 - 1);
                                    string elementContent = (content.EndsWith("/>")) ? content :
                                        content + processContent(null, r, "</" + ctlName);

                                    int mIndex, mLen;
                                    string id = parseId(elementContent, out mIndex, out mLen);
									string[] ctlStr = {/*tag[1]*/getServerControlTagPrefix(), elementContent.Substring(0, mIndex), id,
												elementContent.Substring(mIndex + mLen)};
                                    ServerControlPageContent ctlContent = new ServerControlPageContent(ctlStr);
                                    pageContent.addContent(ctlContent);
                                }
                                else
                                    buff.Append(content);

                                foundTagPrefix = true;
                                break;
                            }
                        }
                        if (!foundTagPrefix)
                        {
                            if (content.Contains("runat=\"server"))
                            {
                                if (pageContent != null)
                                {
                                    htmlContent = new SimpleHtmlPageContent(buff.ToString());
                                    buff = new StringBuilder(2000);
                                    pageContent.addContent(htmlContent);

                                    string ctlName = "";
                                    int p4 = content.IndexOf(' ');
                                    ctlName = content.Substring(1, p4 - 1);
                                    string elementContent = (content.EndsWith("/>")) ? content :
                                        content + processContent(null, r, "</" + ctlName);

                                    int mIndex, mLen;
                                    string id = parseId(elementContent, out mIndex, out mLen);
									string[] ctlStr = {getServerControlTagPrefix(),elementContent.Substring(0, mIndex), id,
												elementContent.Substring(mIndex + mLen)};
                                    ServerControlPageContent ctlContent = new ServerControlPageContent(ctlStr);
                                    pageContent.addContent(ctlContent);
                                }
                                else
                                    buff.Append(content);
                            }
                            else if (endTag != null && content.StartsWith(endTag))
                            {
                                if (pageContent != null)
                                {
                                    htmlContent = new SimpleHtmlPageContent(buff.ToString());
                                    buff = new StringBuilder(2000);
                                    pageContent.addContent(htmlContent);
                                }
                                else
                                    buff.Append(content);
                                break;
                            }
                            else
                                buff.Append(content);
                        }
                    }
				}
				else
					buff.Append(c);
			}
			while (c != 65535);
			if (pageContent != null)
			{
				htmlContent = new SimpleHtmlPageContent(buff.ToString());
				buff = new StringBuilder();
				pageContent.addContent(htmlContent);
			}
			return buff.ToString();
		}

		protected string getServerControlTagPrefix()
		{
			StringBuilder buff = new StringBuilder(512);
			foreach (string[] tag in m_tagPrefixes)
				buff.Append(tag[1]);
			return buff.ToString();
		}

		protected Control getControl(string content)
		{
			string id = parseId(content);
			return (id==null)?null:m_parent.FindControl(id);
		}

		protected string parseId(string content)
		{
			int p,i;
			return parseId(content, out p, out i);
		}
		protected string parseId(string content, out int pos, out int len)
		{
			pos = -1; len = -1;
			Match m = s_contentIdRegex.Match(content);
			string id = null;
			if (m.Success)
			{
				id = m.Groups[1].Value;
				pos = m.Groups[1].Index;
				len = m.Groups[1].Length;
			}
			return id;
		}

		protected string callCode(string content)
		{
			string output = "";
			string callCode = content.Substring(3, content.Length - 5);
			callCode = callCode.Trim();
			Regex pattern = new Regex(@"(\w+)\((.*)\)");
			Match m = pattern.Match(callCode);
			if (m.Success)
			{
				string methodName = m.Groups[1].Value;
				IList methodParams = processCallParams(m.Groups[2].Value);

				MethodInfo methInfo = m_parent.GetType().GetMethod(methodName);
				ParameterInfo[] pInfos = methInfo.GetParameters();
				object[] methodParamArray = new object[pInfos.Length];
				int pos = 0;
				foreach (ParameterInfo pInfo in pInfos)
				{
					Type pType = pInfo.ParameterType;
					methodParamArray[pos] = Cmn.createType(pType, (string)methodParams[pos]);
					pos++;
				}

				output = m_parent.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, m_parent,
					methodParamArray).ToString();
			}

			return output;
		}

		protected IList processCallParams(string paramInfo)
		{
			IList paramList = new ArrayList();
			bool inStr = false;
			StringBuilder buff = new StringBuilder(50);
			foreach (char c in paramInfo)
			{
				if (c == ',' && !inStr)
				{
					paramList.Add(buff.ToString());
					buff.Length = 0;
				}
				else if (c == '"')
					inStr = !inStr;
				else
					buff.Append(c);
			}
			if (buff.Length > 0)
				paramList.Add(buff.ToString());

			return paramList;
		}

		protected string getElement(TextReader r)
		{
			StringBuilder buff = new StringBuilder(50);
			buff.Append("<"); //cuz the caller read that part out allready
			char c;
			do
			{
				c = (char)r.Read();
				buff.Append(c);
				if (c == '>')
					break;
			}
			while ((int)c != 65535);

			return buff.ToString();
		}
	}
}
